#include "shaders.h"
#include "../mathematics.h"

#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
using namespace std;

ShaderProgram::ShaderProgram()
{
    uProgram = glCreateProgram();
}

int ShaderProgram::AddShader(GLenum Type)
{
	GLuint uNewShader = glCreateShader(Type);

	Shaders.push_back(uNewShader);

	return Shaders.size()-1;
}

void ShaderProgram::LoadShader(int nIndex, string sFileName)
{
    char *chShaderSource = NULL;
	int length = 0;

	fstream file;
	file.open(sFileName.c_str(), ios::in | ios::out);
		file.seekg(0, ios::end);
		length = file.tellg();
		file.seekg(0, ios::beg);

		chShaderSource = new char[length+1];
		memset(chShaderSource, '\0', length);
		file.read(chShaderSource, length);
	file.close();

	chShaderSource[length] = '\0';

	glShaderSource(Shaders[nIndex], 1, (const GLchar**)&chShaderSource, NULL);

	delete[] chShaderSource;

	glCompileShader(Shaders[nIndex]);

	GLint status;
	glGetShaderiv(Shaders[nIndex], GL_COMPILE_STATUS, &status);

	if(status == GL_FALSE)
	{
		cout << "Niepoprawna kompilacja shadera. Sciezka: " << sFileName << endl << "Komunikat: " << endl;

		GLint logLength;
		glGetShaderiv(Shaders[nIndex], GL_INFO_LOG_LENGTH, &logLength);

		GLchar *log = new GLchar[logLength];
		glGetShaderInfoLog(Shaders[nIndex], logLength, NULL, log);

		cout << log << endl;

		delete[] log;

		//exit(0);
	}
	return;
}

void ShaderProgram::AttachShader(int nIndex)
{
	glAttachShader(uProgram, Shaders[nIndex]);
}

void ShaderProgram::Link()
{
	if(!glIsProgram(uProgram))
	{
		uProgram = glCreateProgram();
	}

	//

	glBindAttribLocation(uProgram, VERTEX_ATTRIB_LOCATION, "inVertex");
	glBindAttribLocation(uProgram, TEXCOORD_ATTRIB_LOCATION, "inTexCoord");
	glBindAttribLocation(uProgram, NORMAL_ATTRIB_LOCATION, "inNormal");

	//

	glLinkProgram(uProgram);
	glValidateProgram(uProgram);

	GLint status;
	glGetProgramiv(uProgram, GL_LINK_STATUS, &status);

	if(status == GL_FALSE)
	{
		cout << "Niepoprawne laczenie shadera. Komunikat: \n";

		GLint logLength;
		glGetProgramiv(uProgram, GL_INFO_LOG_LENGTH, &logLength);

		GLchar *log = new GLchar[logLength];
		glGetProgramInfoLog(uProgram, logLength, NULL, log);

		cout << log << endl;

		delete[] log;

		//exit(0);
	}
}

void ShaderProgram::Use(bool bUse)
{
	if(bUse)
	{
	    glUseProgram(uProgram);

	    for(unsigned int i = 0; i < UniformsVec3.size(); ++i)
	    {
	        glUniform3f(UniformLocationsVec3[i], UniformsVec3[i][0], UniformsVec3[i][1], UniformsVec3[i][2]);
	    }
	}
	else glUseProgram(0);
}

void ShaderProgram::SetMatrix4(const char *chName, float matrix[16])
{
    glUniformMatrix4fv(glGetUniformLocation(uProgram, chName), 1, GL_TRUE, matrix);
}

void ShaderProgram::SetTexture(const char *chName, GLint nTexture, int nUnitNumber)
{
    int nLocation = glGetUniformLocation(uProgram, chName);

    glActiveTexture(GL_TEXTURE0+nUnitNumber);
    glBindTexture(GL_TEXTURE_2D, nTexture);

    glUniform1i(nLocation, nUnitNumber);
}

void ShaderProgram::SetVector3(const char *chName, float fvVector[3])
{
    glUniform3f(glGetUniformLocation(uProgram, chName), fvVector[0], fvVector[1], fvVector[2]);
}

int ShaderProgram::AddVector3(const char *chName, float *fvVector)
{
    for(unsigned int i = 0; i < UniformsVec3.size(); ++i)
    {
        if(UniformsVec3[i] == NULL)
        {
            UniformsVec3[i] = fvVector;
            UniformLocationsVec3[i] = glGetUniformLocation(uProgram, chName);

            return i;
        }
    }
    UniformLocationsVec3.push_back(glGetUniformLocation(uProgram, chName));
    UniformsVec3.push_back(fvVector);

    return UniformsVec3.size()-1;
}

void ShaderProgram::DelVector3(int nIndex)
{
    UniformLocationsVec3[nIndex] = 0;
    UniformsVec3[nIndex] = NULL;
}
